﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using Common.Utilities.Dispatcher;

namespace Workflows.Components.FileManipulation
{
    /// <summary>
    /// folder sync worker
    /// </summary>
    public class FolderSyncWorker:IWorker
    {
        private Thread _WorkerThread;
        private FolderSyncInstruction _Instruction;
        private FolderSyncOutput _Output;
        private RunContext _Context;
        private Guid _TicketID;
        private string _WorkflowName;
        private string _ComponentName;

        /// <summary>
        /// 
        /// </summary>
        public string ComponentName
        {
            get { return _ComponentName; }
        }

        /// <summary>
        /// 
        /// </summary>
        public string WorkflowName
        {
            get { return _WorkflowName; }
        }

        private int _TotalFilesToSync;
        private int _TotalFilesSynced;
        private int _NewFileCount;
        private int _ChangedFileCount;
        private int _DeletedFileCount;

        /// <summary>
        /// handler
        /// </summary>
        public Thread WorkerThread
        {
            get { return _WorkerThread; }
        }

        /// <summary>
        /// instruction
        /// </summary>
        public InstructionBase Instruction
        {
            get { return _Instruction; }
        }

        /// <summary>
        /// output
        /// </summary>
        public ResultBase Output
        {
            get { return _Output; }
        }

        /// <summary>
        /// activity type
        /// </summary>
        public Type ComponentType
        {
            get { return typeof(FolderSyncComponent); }
        }

        /// <summary>
        /// context
        /// </summary>
        public RunContext Context
        {
            get { return _Context; }
            set { _Context = value; }
        }

        /// <summary>
        /// init
        /// </summary>
        /// <param name="ticketGuid"></param>
        /// <param name="request"></param>
        public void SetInstruction(Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._TicketID = ticketGuid;
            this._Instruction = (FolderSyncInstruction) request;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        /// <summary>
        /// init
        /// </summary>
        /// <param name="context"></param>
        /// <param name="ticketGuid"></param>
        /// <param name="request"></param>
        public void SetInstruction(RunContext context, Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._Context = context;
            this._TicketID = ticketGuid;
            this._Instruction = (FolderSyncInstruction) request;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        /// <summary>
        /// sync method call
        /// </summary>
        public void DoWork()
        {
            if (this.WorkerStartedEvent != null)
                this.WorkerStartedEvent(this._TicketID, this, this.GetType(), this._Instruction);
            this._WorkerThread = Thread.CurrentThread;
            this.SyncFolder();
        }

        /// <summary>
        /// async method call
        /// </summary>
        public void DoWorkAsync()
        {
            if (this.WorkerStartedEvent != null)
                this.WorkerStartedEvent(this._TicketID, this, this.GetType(), this._Instruction);
            this._WorkerThread = new Thread(new ThreadStart(this.SyncFolder));
            this._WorkerThread.Start();
        }

        /// <summary>
        /// cancel
        /// </summary>
        public void CancelWork()
        {
            if(this._WorkerThread !=null && this._WorkerThread.IsAlive)
            {
                this._WorkerThread.Abort();
                this._WorkerThread.Join();
                if(this.WorkerCancelledEvent !=null)
                {
                    this.WorkerCancelledEvent(this._TicketID, this.GetType(), this._Instruction);
                }
            }
        }

        public event WorkerStarted WorkerStartedEvent;
        public event WorkerFinished WorkerFinishedEvent;
        public event WorkerHasError WorkerErredEvent;
        public event WorkerCancelled WorkerCancelledEvent;
        public event WorkerStatusChanged WorkerStatusChangedEvent;

        #region sync folder
        private void SyncFolder()
        {
            this._TotalFilesSynced = 0;
            this._TotalFilesToSync = 0;
            this._NewFileCount = 0;
            this._ChangedFileCount = 0;
            this._DeletedFileCount = 0;
            try
            {
                DirectoryInfo dirInfo1 = new DirectoryInfo(this._Instruction.SrcFolderPath);
                FileInfo[] files = null;
                if(this._Instruction.IncludeSubFolders)
                {
                    files = dirInfo1.GetFiles("*.*", SearchOption.AllDirectories);
                }
                else
                {
                    files = dirInfo1.GetFiles("*.*", SearchOption.TopDirectoryOnly);
                }
                if (files != null && files.Length > 0)
                {
                    this._TotalFilesToSync = files.Length;
                    List<string> extensionsToExclude=new List<string>();
                    if(!string.IsNullOrEmpty(this._Instruction.ExcludedFileExtensions))
                    {
                        string[] extensions = this._Instruction.ExcludedFileExtensions.Split(new char[] {','});
                        foreach(string extension in extensions)
                        {
                            string ext = extension.ToLower();
                            if(extension.StartsWith("*."))
                            {
                                ext = "." + extension.Substring(2);
                            }
                            extensionsToExclude.Add(ext);
                        }
                    }
                    this.CopyFolder(this._Instruction.SrcFolderPath, this._Instruction.SrcFolderPath,
                                    this._Instruction.TargetFolderPath, extensionsToExclude,
                                    this._Instruction.IncludeSubFolders);
                }

                this._Output=new FolderSyncOutput(this._Instruction.OwnerComponentID,this._TicketID,this._NewFileCount,this._ChangedFileCount,this._DeletedFileCount);
                if(this.WorkerFinishedEvent !=null)
                {
                    this.WorkerFinishedEvent(this._TicketID, this.GetType(), this._Instruction, this._Output);
                }
            }
            catch(Exception ex)
            {
                if(this.WorkerErredEvent !=null)
                {
                    this.WorkerErredEvent(this._TicketID, this.GetType(), this._Instruction, ex.ToString(),true);
                }
            }
        }

        private void CopyFolder(string srcFolderPath, string srcRootFolderPath, string tgtRootFolderPath, List<string> fileExtensionsToExclude,  bool includeSubFolder)
        {
            DirectoryInfo srcFolderInfo = new DirectoryInfo(srcFolderPath);
            FileInfo[] files = srcFolderInfo.GetFiles("*.*", SearchOption.TopDirectoryOnly);
            if (files != null && files.Length > 0)
            {
                this.CopyFiles(files, srcRootFolderPath, tgtRootFolderPath, fileExtensionsToExclude);
            }

            DirectoryInfo[] childDirs = srcFolderInfo.GetDirectories();
            if (childDirs != null && childDirs.Length > 0)
            {
                foreach (DirectoryInfo childDirInfo in childDirs)
                {
                    this.CopyFolder(childDirInfo.FullName, srcRootFolderPath, tgtRootFolderPath, fileExtensionsToExclude, includeSubFolder);
                }
            }
        }

        private void CopyFiles(FileInfo[] files, string srcRootFolderPath, string tgtRootFolderPath, List<string> fileExtensionsToExclude)
        {
            foreach (FileInfo fileInfo in files)
            {
                if (!fileExtensionsToExclude.Contains(fileInfo.Extension.ToLower()))
                {
                    string srcFilePath = fileInfo.FullName;
                    string srcFolderPath = Path.GetDirectoryName(srcFilePath);
                    string tgtFolderPath = tgtRootFolderPath;
                    if (tgtFolderPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
                    {
                        tgtFolderPath = tgtFolderPath.Substring(0, tgtFolderPath.Length - 1);
                    }
                    if (srcFolderPath.ToLower().StartsWith(srcRootFolderPath.ToLower()))
                    {
                        string relativeSubFolderPath = srcFolderPath.Substring(srcRootFolderPath.Length);
                        if (relativeSubFolderPath.StartsWith(Path.DirectorySeparatorChar.ToString()))
                        {
                            relativeSubFolderPath = relativeSubFolderPath.Substring(1);
                        }
                        if (relativeSubFolderPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
                        {
                            relativeSubFolderPath = relativeSubFolderPath.Substring(0, relativeSubFolderPath.Length - 1);
                        }
                        string[] subFolderNames = relativeSubFolderPath.Split(new char[] { Path.DirectorySeparatorChar });
                        for (int i = 0; i < subFolderNames.Length; i++)
                        {
                            tgtFolderPath += Path.DirectorySeparatorChar + subFolderNames[i];
                            if (!Directory.Exists(tgtFolderPath))
                            {
                                Directory.CreateDirectory(tgtFolderPath);
                            }
                        }
                    }
                    string tgtFilePath = Path.Combine(tgtFolderPath, fileInfo.Name);
                    if (File.Exists(tgtFilePath))
                    {
                        FileInfo tgtFileInfo = new FileInfo(tgtFilePath);
                        if (tgtFileInfo.LastWriteTime < fileInfo.LastWriteTime)
                        {
                            this._ChangedFileCount++;
                            fileInfo.CopyTo(tgtFilePath, true);
                        }
                    }
                    else
                    {
                        this._NewFileCount++;
                        fileInfo.CopyTo(tgtFilePath);
                    }
                }

                this._TotalFilesSynced++;
                if(this._TotalFilesSynced % 100==0)
                {
                    if(this.WorkerStatusChangedEvent !=null)
                    {
                        int percent = this._TotalFilesSynced * 100 / this._TotalFilesToSync;

                        this.WorkerStatusChangedEvent(this._TicketID, this.GetType(), this._Instruction, this.WorkflowName, this.ComponentName,
                                                      string.Format(
                                                          "Synchronized file {0}... #{1} of {2}, {3} files added, {4} files updated",
                                                          fileInfo.Name, this._TotalFilesSynced, this._TotalFilesToSync,
                                                          this._NewFileCount, this._ChangedFileCount),
                                                      percent);
                    }
                }
                
            }
        }
        #endregion
    }
}
